#ifndef PYXX_FUNCTION_H
#define PYXX_FUNCTION_H

#include <boost/function.hpp>
#include <pyxx/template.h>
#include <pyxx/pointer.h>


template<typename Unknown>
struct boost_function_detail {};

template<typename Return>
struct boost_function_detail< boost::function0<Return> >
{
    typedef Return return_type;
    typedef boost::function0<Return> fn_type;
    typedef boost_function_detail<fn_type> this_type;
    static return_type callit(boost::python::object obj)
    {
        try
        {
            using namespace boost::python;
            return extract<return_type>(obj());
        }
        catch(boost::python::error_already_set&)
        {
            python_error::raise();
        }
    }

    static fn_type bind(boost::python::object obj)
    {
        return boost::bind(&this_type::callit, obj);
    }

};

template<>
struct boost_function_detail< boost::function0<void> >
{
    typedef void return_type;
    typedef boost::function0<void> fn_type;
    typedef boost_function_detail<fn_type> this_type;
    static return_type callit(boost::python::object obj)
    {
        try
        {
            using namespace boost::python;
            obj();
        }
        catch(boost::python::error_already_set&)
        {
            python_error::raise();
        }

    }

    static fn_type bind(boost::python::object obj)
    {
        return boost::bind(&this_type::callit, obj);
    }
};

template<typename Return, typename A1>
struct boost_function_detail< boost::function1<Return, A1> >
{
    typedef Return return_type;
    typedef boost::function1<Return, A1> fn_type;
    typedef boost_function_detail<fn_type> this_type;
    static return_type callit(boost::python::object obj, A1 a1)
    {
        try
        {
            using namespace boost::python;
            return extract<return_type>(obj(pointer_safe<A1>::get(a1)));
        }
        catch(boost::python::error_already_set&)
        {
            python_error::raise();
        }

    }

    static fn_type bind(boost::python::object obj)
    {
        return boost::bind(&this_type::callit, obj, _1);
    }
};

template<typename A1>
struct boost_function_detail< boost::function1<void, A1> >
{
    typedef void return_type;
    typedef boost::function1<void, A1> fn_type;
    typedef boost_function_detail<fn_type> this_type;
    static return_type callit(boost::python::object obj, A1 a1)
    {
        try
        {
            using namespace boost::python;
            obj(pointer_safe<A1>::get(a1)); 
        }
        catch(boost::python::error_already_set&)
        {
            python_error::raise();
        }

    }

    static fn_type bind(boost::python::object obj)
    {
        return boost::bind(&this_type::callit, obj, _1);
    }
};


template<typename fn_type>
struct expose_boost_function : 
    public expose_template_type_base< fn_type >
{
    typedef boost_function_detail<fn_type> detail_type;
    typedef expose_template_type_base< fn_type > base_type;
    typedef expose_boost_function< fn_type > this_type;
        
    expose_boost_function()
    {
        if( !base_type::wrapped() )
        {
            using namespace boost::python;

            class_< fn_type >( typeid(fn_type).name(), no_init)
               .def("__call__", &fn_type::operator () )
               ;
            
            converter::registry::push_back(
                   &this_type::convertible,
                   &this_type::construct,
                   type_id<fn_type>() );


        }

    }

    static void * convertible( PyObject * obj )
    {
        if( PyCallable_Check(obj) )
            return obj;
        else
            return 0;
    }


    static void construct( PyObject * obj, 
            boost::python::converter::rvalue_from_python_stage1_data * data)
    {
        using namespace boost::python;
        fn_type fn = detail_type::bind( object( handle<>( borrowed(obj) ) ) );

        typedef converter::rvalue_from_python_storage<fn_type> storage_t;
        storage_t * the_storage = reinterpret_cast<storage_t*>(data);
        void * memory_check = the_storage->storage.bytes;
        new (memory_check) fn_type(fn);
        data->convertible = memory_check;
    }
};


template<typename Return>
struct expose_template_type< boost::function0<Return> > :
    public expose_boost_function< boost::function0<Return> > 
{
};

template<>
struct expose_template_type< boost::function0<void> > :
    public expose_boost_function< boost::function0<void> > 
{
};

template<typename Return, typename A1>
struct expose_template_type< boost::function1<Return, A1> > :
    public expose_boost_function< boost::function1<Return, A1> > 
{
};

template<typename A1>
struct expose_template_type< boost::function1<void, A1> > :
    public expose_boost_function< boost::function1<void, A1> > 
{
};

#endif
